//
// Created by Lebron Sn on 2017/2/6.
//
#include "bs.h"
#include "bzip2/bzlib.h"

static off_t offtin(u_char *buf)
{
    off_t y;

    y=buf[7]&0x7F;
    y=y*256;y+=buf[6];
    y=y*256;y+=buf[5];
    y=y*256;y+=buf[4];
    y=y*256;y+=buf[3];
    y=y*256;y+=buf[2];
    y=y*256;y+=buf[1];
    y=y*256;y+=buf[0];

    if(buf[7]&0x80) y=-y;

    return y;
}

#include <jni.h>
#include <stdlib.h>
#include <string.h>

    JNIEXPORT jint JNICALL
    Java_com_yoogor_bsdiffpatch_MainActivity_patch
        (JNIEnv *env, jobject instance, jstring oldpath_, jstring newpath_,jstring patch_) {
    const char* argv[4];
    argv[0] = "bspatch";
    argv[1] = (*env)->GetStringUTFChars(env,oldpath_, 0);
    argv[2] = (*env)->GetStringUTFChars(env,newpath_, 0);
    argv[3] = (*env)->GetStringUTFChars(env, patch_, 0);
    mypatch(4,argv);
    (*env)->ReleaseStringUTFChars(env,oldpath_, argv[1]);
    (*env)->ReleaseStringUTFChars(env,newpath_, argv[2]);
    (*env)->ReleaseStringUTFChars(env,patch_,argv[3]);
    free(argv);
    return 0;
}

JNIEXPORT jint JNICALL
Java_com_yoogor_bsdiffpatch_MainActivity_diff
        (JNIEnv *env, jobject instance, jstring oldpath_, jstring newpath_, jstring patch_) {
    const char* argv[4];
    argv[0] = "bsdiff";
    argv[1] = (*env)->GetStringUTFChars(env,oldpath_, 0);
    argv[2] = (*env)->GetStringUTFChars(env,newpath_, 0);
    argv[3] = (*env)->GetStringUTFChars(env, patch_, 0);
    mydiff(4,argv);
    (*env)->ReleaseStringUTFChars(env,oldpath_, argv[1]);
    (*env)->ReleaseStringUTFChars(env,newpath_, argv[2]);
    (*env)->ReleaseStringUTFChars(env,patch_,argv[3]);
    free(argv[0]);
    free(argv);
    return 0;
}


const size_t BUFFER_SIZE = 4096;

static int bz2_read(const struct bspatch_stream* stream, void* buffer, int length)
{
    int n;
    int bz2err;
    bz_stream* zip;

    if (length == 0) {
        return 0;
    }

    zip = (bz_stream*)stream->opaque;
    zip->next_out = (char*)buffer;
    zip->avail_out = length;

    int bz2Ret = BZ2_bzDecompress(zip);

    if (bz2Ret != BZ_OK && (bz2Ret != BZ_STREAM_END || zip->avail_out > 0))
        return -1;

    return 0;
}

//
//int bspatch(const u_char* old, off_t oldsize, u_char* new, off_t newsize, struct bspatch_stream* stream)
//{
//    u_char buf[8];
//    off_t oldpos,newpos;
//    off_t ctrl[3];
//    off_t i;
//
//    oldpos=0;newpos=0;
//    while(newpos<newsize) {
//        /* Read control data */
//        for(i=0;i<=2;i++) {
//            if (stream->read(stream, buf, 8))
//                return -1;
//            ctrl[i]=offtin(buf);
//        };
//
//        /* Sanity-check */
//        if(newpos+ctrl[0]>newsize)
//            return -1;
//
//        /* Read diff string */
//        if (stream->read(stream, new + newpos, ctrl[0]))
//            return -1;
//
//        /* Add old data to diff string */
//        for(i=0;i<ctrl[0];i++)
//            if((oldpos+i>=0) && (oldpos+i<oldsize))
//                new[newpos+i]+=old[oldpos+i];
//
//        /* Adjust pointers */
//        newpos+=ctrl[0];
//        oldpos+=ctrl[0];
//
//        /* Sanity-check */
//        if(newpos+ctrl[1]>newsize)
//            return -1;
//
//        /* Read extra string */
//        if (stream->read(stream, new + newpos, ctrl[1]))
//            return -1;
//
//        /* Adjust pointers */
//        newpos+=ctrl[1];
//        oldpos+=ctrl[2];
//    };
//
//    return 0;
//}

JNIEXPORT jbyteArray JNICALL Java_com_yoogor_bsdiffpatch_MainActivity_bsdiffPatch
        (JNIEnv *env, jobject self, jbyteArray origin, jbyteArray patch){
    jclass newExcCls;
    jbyte* outPtr;
    struct bspatch_stream stream;
    bz_stream zip;

    jbyte* originPtr = (*env)->GetByteArrayElements(env, origin, NULL);
    size_t originLength = (*env)->GetArrayLength(env, origin);
    jbyte* patchPtr = (*env)->GetByteArrayElements(env, patch, NULL);
    size_t patchLength = (*env)->GetArrayLength(env, patch);
    jbyteArray ret = NULL;

    if (patchLength < 32) {
        newExcCls = (*env)->FindClass(env,"java/lang/Error");
        if (newExcCls != NULL) /* Unable to find the new exception class, give up. */
            (*env)->ThrowNew(env,newExcCls, "Corrupt patch");
        (*env)->ReleaseByteArrayElements(env, origin, originPtr, JNI_ABORT);
        (*env)->ReleaseByteArrayElements(env, patch, patchPtr, JNI_ABORT);
        return NULL;
    }
    off_t newsize=offtin((u_char*)patchPtr + 16);
    if (memcmp(patchPtr, "ENDSLEY/BSDIFF43", 16) != 0 || newsize<0) {
        newExcCls = (*env)->FindClass(env, "java/lang/Error");
        if (newExcCls != NULL) /* Unable to find the new exception class, give up. */
            (*env)->ThrowNew(env, newExcCls, "Corrupt patch");
        (*env)->ReleaseByteArrayElements(env, origin, originPtr, JNI_ABORT);
        (*env)->ReleaseByteArrayElements(env, patch, patchPtr, JNI_ABORT);
        return NULL;
    }
    ret = (*env)->NewByteArray(env, newsize);
    if (ret == NULL) {
        return NULL; //  out of memory error thrown
    }
    outPtr = (*env)->GetByteArrayElements(env, ret, NULL);

    zip.bzalloc = NULL;
    zip.bzfree = NULL;
    zip.opaque = NULL;
    BZ2_bzDecompressInit(&zip, 0, 1);

    zip.next_in = (char*)patchPtr + 24;
    zip.avail_in = patchLength - 24;

    stream.read = bz2_read;
    stream.opaque = &zip;
    if (bspatch((const u_char*)originPtr, originLength, (u_char*)outPtr, newsize, &stream)) {
        newExcCls = (*env)->FindClass(env, "java/lang/Error");
        if (newExcCls != NULL) /* Unable to find the new exception class, give up. */
            (*env)->ThrowNew(env, newExcCls, "bspatch");
    }

    BZ2_bzDecompressEnd(&zip);

    (*env)->ReleaseByteArrayElements(env, ret, outPtr, 0);
    (*env)->ReleaseByteArrayElements(env, origin, originPtr, JNI_ABORT);
    (*env)->ReleaseByteArrayElements(env, patch, patchPtr, JNI_ABORT);
    return ret;
}
